package org.jeecg.access.platform.service.impl;

import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.jeecg.access.platform.entity.BaseUserInfo;
import org.jeecg.access.platform.entity.BaseWorkData;
import org.jeecg.access.platform.service.BaseGetDataService;
import org.jeecg.access.platform.util.HttpRequestUtil;
import org.jeecg.util.DateTimeUtil;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 数据同步服务抽象基类
 * <p>
 * 实现BaseGetDataService接口，定义用户信息和作品数据同步的通用流程框架。
 * 采用模板方法模式，将不变的流程逻辑在抽象类中实现，将具体平台的差异化逻辑（如API调用、数据解析）
 * 延迟到子类实现，确保不同平台同步流程的一致性，同时保留平台特有逻辑的灵活性。
 * </p>
 * <p>
 * 核心特性：
 * 1. 统一的用户信息和作品数据同步流程
 * 2. 内置分批处理机制，减少内存占用
 * 3. 支持请求重试策略，提高接口稳定性
 * 4. 优化的资源使用，避免不必要的对象创建
 * </p>
 */
@Slf4j
public abstract class AbstractGetDataServiceImpl implements BaseGetDataService {

    /**
     * HTTP请求工具类，用于发送HTTP请求（包含重试、超时控制等基础能力）
     * 由构造函数注入，避免在子类中重复创建
     */
    protected final HttpRequestUtil httpRequestUtil;

    /**
     * JSON序列化/反序列化工具，用于处理API响应数据
     * 由构造函数注入，全局共享一个实例，减少对象创建开销
     */
    protected final ObjectMapper objectMapper;

    /**
     * 批量处理的批次大小，控制单次数据库操作的数据量
     * 合理的批次大小可平衡数据库性能和内存占用，默认50条/批
     */
    protected static final int BATCH_PROCESS_SIZE = 50;

    /**
     * 构造方法（依赖注入）
     * <p>
     * 通过构造函数注入必要的工具类，确保抽象类及子类使用统一的工具实例，
     * 避免多次创建导致的资源浪费，同时符合依赖注入原则。
     * </p>
     *
     * @param httpRequestUtil HTTP请求工具类实例，提供HTTP通信能力
     * @param objectMapper    JSON处理工具实例，提供JSON序列化/反序列化能力
     */
    protected AbstractGetDataServiceImpl(HttpRequestUtil httpRequestUtil, ObjectMapper objectMapper) {
        this.httpRequestUtil = httpRequestUtil;
        this.objectMapper = objectMapper;
    }

    /**
     * 同步用户信息主流程（模板方法）
     * <p>
     * 定义用户信息同步的完整流程，步骤如下：
     * 1. 查询待同步用户列表
     * 2. 遍历用户列表，逐个处理（含请求、解析、校验）
     * 3. 收集处理成功的用户信息
     * 4. 批量更新到数据库
     * </p>
     * <p>
     * 流程特点：
     * - 统一的错误处理，单个用户处理失败不影响整体流程
     * - 全局时间戳，确保同一批次数据更新时间一致
     * - 延迟批量入库，减少数据库交互次数
     * </p>
     */
    @Override
    public void saveUserInfo() {
        log.info("===== 开始执行用户信息同步任务 =====");

        // 1. 查询待同步用户列表（子类实现具体查询逻辑）
        List<? extends BaseUserInfo> userList = fetchUserInfoList();
        if (userList == null || userList.isEmpty()) {
            log.warn("无待同步的用户信息，任务终止");
            return;
        }
        log.info("从数据库查询到 {} 个待同步用户，开始处理", userList.size());

        // 用于存放处理成功的用户信息（延迟批量入库）
        List<BaseUserInfo> successUserList = new ArrayList<>(userList.size()); // 预初始化容量，减少扩容开销
        String nowTime = DateTimeUtil.getCurrentTimestamp(); // 统一更新时间戳，减少重复计算

        // 2. 遍历用户列表，逐个处理
        for (int i = 0; i < userList.size(); i++) {
            BaseUserInfo user = userList.get(i);
            if (user == null) {
                log.warn("第 {} 个用户数据为空，跳过处理", i + 1);
                continue;
            }

            // 调用公共方法处理单个用户（复用逻辑，减少代码冗余）
            BaseUserInfo processedUser = processSingleUser(user, nowTime);
            if (processedUser != null) {
                successUserList.add(processedUser);
            }
        }

        // 3. 批量更新成功的用户信息到数据库
        if (!successUserList.isEmpty()) {
            log.info("共处理成功 {} 个用户，开始批量更新到数据库", successUserList.size());
            batchUpdateUserInfo(successUserList);
            log.info("用户信息同步任务完成，成功更新 {} 条数据", successUserList.size());
        } else {
            log.warn("所有用户处理失败，未执行数据库更新");
        }
        log.info("===== 用户信息同步任务结束 =====");
    }

    /**
     * 处理单个用户信息同步（公共方法）
     * <p>
     * 封装单个用户的同步逻辑，包括请求头组装、请求发送、响应解析等步骤，
     * 供saveUserInfo方法循环调用，提高代码复用性。
     * </p>
     *
     * @param user     待处理的用户信息对象
     * @param nowTime  统一的更新时间戳
     * @return 处理成功的用户信息（已更新同步数据），null表示处理失败
     */
    protected BaseUserInfo processSingleUser(BaseUserInfo user, String nowTime) {
        String userId = user.getId();
        log.debug("开始处理用户 [{}]", userId);

        try {
            // 2.1 组装请求头（子类实现平台特有头信息）
            Map<String, String> headers = createUserRequestHeaders(user);
            if (headers == null) {
                headers = new HashMap<>(4); // 初始化固定容量，减少扩容
            }

            // 2.2 组装请求报文（子类实现平台特有参数）
            String requestBody = buildUserRequestData(user);
            if (requestBody == null || requestBody.trim().isEmpty()) {
                log.warn("用户 [{}] 请求报文为空，跳过", userId);
                return null;
            }

            // 2.3 获取重试决策器（子类可自定义重试策略）
            HttpRequestUtil.RetryDecision retryDecision = createUserRetryDecision(user);
            if (retryDecision == null) {
                retryDecision = new DefaultRetryDecision(); // 使用默认策略
                log.debug("用户 [{}] 未提供重试决策器，使用默认实现", userId);
            }

            // 2.4 发送请求（复用HTTP工具类，含基础重试逻辑）
            String response = httpRequestUtil.sendRequest(
                    getUserApiUrl(user),  // 平台特有API地址
                    getUserRequestMethod(),  // 平台特有请求方法
                    headers,
                    requestBody,
                    retryDecision
            );

            // 2.5 校验响应有效性
            if (response == null || response.trim().isEmpty()) {
                log.warn("用户 [{}] 经过重试后仍无有效响应，跳过", userId);
                return null;
            }

            // 2.6 解析响应数据（子类实现平台特有解析逻辑）
            BaseUserInfo updatedUser = parseUserResponse(user, response);
            if (updatedUser == null) {
                log.warn("用户 [{}] 响应解析失败，跳过", userId);
                return null;
            }

            // 2.7 统一设置更新时间（确保批次内时间一致）
            updatedUser.setUpdateTime(nowTime);

            log.debug("用户 [{}] 处理成功", userId);
            return updatedUser;

        } catch (Exception e) {
            // 单个用户处理异常不中断整体流程
            log.error("用户 [{}] 处理异常，跳过：{}", userId, e.getMessage(), e);
            return null;
        }
    }

    /**
     * 默认重试决策器（不进行业务重试）
     * <p>
     * 当子类未提供重试策略时使用，仅在网络错误或HTTP 5xx错误时由HTTP工具类重试，
     * 避免无意义的业务重试消耗资源。
     * </p>
     */
    private class DefaultRetryDecision implements HttpRequestUtil.RetryDecision {
        @Override
        public boolean needRetry(int responseCode, String response) {
            // 默认不进行业务重试（HTTP工具类会处理网络层面的重试）
            return false;
        }
    }

    /**
     * 作品数据同步主方法
     * 根据dataNum参数决定使用全量查询还是部分查询
     *
     * @param dataNum 数据查询类型标识：
     *                1 - 使用全量查询(fetchUserAllWorks)
     *                非1 - 使用部分查询(fetchUserWorks)
     */
    @Override
    public void saveWorksData(Integer dataNum) {
        log.info("===== 开始执行作品数据同步任务 =====");

        // 1. 初始化全局作品列表（预分配容量，减少动态扩容）
        List<BaseWorkData> allWorkDataList = new ArrayList<>();
        // 获取统一时间戳，确保所有同步数据使用相同的时间标记
        String nowTime = DateTimeUtil.getCurrentTimestamp();

        // 2. 查询需要同步作品的用户列表
        List<? extends BaseUserInfo> userInfoList = fetchUserInfoList();
        // 检查用户列表是否为空，为空则终止任务
        if (userInfoList == null || userInfoList.isEmpty()) {
            log.warn("未查询到任何用户信息，作品同步任务终止");
            return;
        }
        log.info("共查询到 {} 个用户，开始处理作品数据", userInfoList.size());

        // 3. 遍历用户处理作品数据
        for (int i = 0; i < userInfoList.size(); i++) {
            // 获取当前循环的用户信息
            BaseUserInfo userInfo = userInfoList.get(i);

            // 跳过空用户数据
            if (userInfo == null) {
                log.warn("第 {} 个用户数据为空，跳过处理", i + 1);
                continue;
            }

            // 获取用户ID用于日志和业务处理
            String userId = userInfo.getId();

            try {
                log.debug("开始处理用户 [{}] 的作品（{}/{}）", userId, i + 1, userInfoList.size());

                // 3.1 根据dataNum参数决定调用的查询方法
                List<? extends BaseWorkData> userWorkList;

                // 调用全量查询方法（含分页逻辑）
                userWorkList = fetchUserAllWorks(userInfo, nowTime,dataNum);


                // 3.2 检查当前用户是否有作品数据
                if (userWorkList == null || userWorkList.isEmpty()) {
                    log.info("用户 [{}] 未查询到作品数据，跳过", userId);
                    continue;
                }

                // 3.3 将用户作品添加到全局列表（延迟批量入库）
                allWorkDataList.addAll(userWorkList);
                log.debug("用户 [{}] 作品处理完成，累计全局作品数：{}", userId, allWorkDataList.size());

            } catch (Exception e) {
                // 单个用户处理异常不中断整体流程，记录错误日志后继续处理下一个用户
                log.error("用户 [{}] 作品处理异常，跳过：{}", userId, e.getMessage(), e);
            }
        }

        // 4. 批量插入所有作品数据（分批处理，避免内存溢出）
        if (!allWorkDataList.isEmpty()) {
            log.info("作品数据收集完成，共 {} 条，开始批量插入数据库", allWorkDataList.size());

            // 调用分批处理工具方法
            processInBatches(allWorkDataList, new BatchProcessor<BaseWorkData>() {
                @Override
                public void process(List<BaseWorkData> batch, int currentBatch, int totalBatch) throws Exception {
                    // 执行批次更新
                    batchUpdateWorksData(batch);
                    log.info("作品批量插入批次 [{}]/[{}] 完成，处理 {} 条数据",
                            currentBatch, totalBatch, batch.size());
                }
            });

            log.info("作品数据同步任务完成，累计成功插入 {} 条数据", allWorkDataList.size());
        } else {
            log.warn("未收集到任何有效作品数据，未执行数据库插入");
        }

        log.info("===== 作品数据同步任务结束 =====");
    }

    /**
     * 分批处理列表数据（通用工具方法）
     * <p>
     * 将大列表按BATCH_PROCESS_SIZE拆分为多个小批次，逐个处理，
     * 减少单次处理的内存占用，尤其适用于大数据量场景。
     * </p>
     *
     * @param dataList 待处理的完整列表
     * @param processor 批次处理器，由调用方实现具体处理逻辑
     * @param <T> 列表元素类型
     */
    protected <T> void processInBatches(List<T> dataList, BatchProcessor<T> processor) {
        if (dataList == null || dataList.isEmpty()) {
            log.debug("批量处理的列表为空，直接返回");
            return;
        }

        int totalSize = dataList.size();
        int batchCount = (totalSize + BATCH_PROCESS_SIZE - 1) / BATCH_PROCESS_SIZE; // 计算总批次数

        for (int batchIndex = 0; batchIndex < batchCount; batchIndex++) {
            int start = batchIndex * BATCH_PROCESS_SIZE;
            int end = Math.min(start + BATCH_PROCESS_SIZE, totalSize);

            // 手动构建子列表（避免使用subList的视图特性导致的并发问题）
            List<T> batch = new ArrayList<>(BATCH_PROCESS_SIZE);
            for (int i = start; i < end; i++) {
                batch.add(dataList.get(i));
            }

            try {
                processor.process(batch, batchIndex + 1, batchCount);
                log.debug("批次 [{}]/[{}] 处理完成，包含 {} 条数据",
                        batchIndex + 1, batchCount, batch.size());
            } catch (Exception e) {
                log.error("批次 [{}]/[{}] 处理失败：{}",
                        batchIndex + 1, batchCount, e.getMessage(), e);
            }
        }
    }

    /**
     * 批次处理器接口（内部接口）
     * <p>
     * 定义批次处理的抽象方法，由调用方实现具体的批次处理逻辑（如数据库批量插入）。
     * </p>
     * @param <T> 处理的数据类型
     */
    protected interface BatchProcessor<T> {
        /**
         * 处理单个批次的数据
         * @param batch 当前批次的数据列表
         * @param currentBatch 当前批次编号（从1开始）
         * @param totalBatch 总批次数
         * @throws Exception 处理过程中可能抛出的异常
         */
        void process(List<T> batch, int currentBatch, int totalBatch) throws Exception;
    }

    // ========================== 用户信息同步抽象方法 ==========================

    /**
     * 从业务数据库查询待同步的用户列表
     * <p>
     * 子类需实现具体的查询逻辑，返回需要同步的用户信息列表。
     * 建议查询时添加过滤条件（如未同步或超过一定时间未更新的用户），减少无效处理。
     * </p>
     *
     * @return 待同步的用户信息列表，若无可返回空列表（避免null判断）
     */
    protected abstract List<? extends BaseUserInfo> fetchUserInfoList();

    /**
     * 为单个用户创建请求头
     * <p>
     * 子类需实现平台特有请求头的组装，如认证信息（Token）、Content-Type等。
     * 建议复用通用头信息，仅添加用户级别的特殊头（如用户Cookie）。
     * </p>
     *
     * @param user 待同步的用户信息对象
     * @return 组装好的请求头Map（键为头名称，值为头内容）
     */
    protected abstract Map<String, String> createUserRequestHeaders(BaseUserInfo user);

    /**
     * 为单个用户组装请求报文
     * <p>
     * 子类需实现平台特有请求参数的组装，返回JSON或Form表单等格式的字符串。
     * 建议对参数进行校验，避免无效请求。
     * </p>
     *
     * @param user 待同步的用户信息对象
     * @return 组装好的请求报文字符串（非空，否则会被跳过）
     */
    protected abstract String buildUserRequestData(BaseUserInfo user);

    /**
     * 获取用户信息API的URL
     * <p>
     * 子类需返回平台用户信息接口的URL，支持根据用户信息动态生成URL（如包含用户ID的路径参数）。
     * </p>
     *
     * @param user 待同步的用户信息对象
     * @return 用户信息API的完整URL
     */
    protected abstract String getUserApiUrl(BaseUserInfo user);

    /**
     * 获取用户信息的请求方法
     * <p>
     * 子类需返回平台用户信息接口的HTTP请求方法（如GET、POST等）。
     * </p>
     *
     * @return HTTP请求方法字符串（大写，如"GET"、"POST"）
     */
    protected abstract String getUserRequestMethod();

    /**
     * 创建用户信息请求的重试决策器
     * <p>
     * 子类可实现平台特有的重试策略，根据响应码或响应内容判断是否需要重试。
     * 若返回null，将使用默认决策器（不重试）。
     * </p>
     *
     * @param user 当前处理的用户对象
     * @return 重试决策器实例，或null使用默认实现
     */
    protected abstract HttpRequestUtil.RetryDecision createUserRetryDecision(BaseUserInfo user);

    /**
     * 从响应报文中解析数据并组装用户信息
     * <p>
     * 子类需实现平台特有响应的解析逻辑，将API返回的字符串解析为用户信息对象，
     * 并更新原有用户对象的属性（如粉丝数、昵称等）。
     * </p>
     *
     * @param originalUser 原始用户信息对象（包含待同步的基础信息）
     * @param response API返回的响应报文字符串
     * @return 解析后更新的用户信息对象，或null表示解析失败
     */
    protected abstract BaseUserInfo parseUserResponse(BaseUserInfo originalUser, String response);

    /**
     * 批量更新处理成功的用户信息到数据库
     * <p>
     * 子类需实现用户信息的批量入库逻辑（如批量INSERT或UPDATE），
     * 建议使用数据库的批量操作API，减少SQL执行次数。
     * </p>
     *
     * @param successUserList 处理成功的用户信息列表（非空）
     */
    protected abstract void batchUpdateUserInfo(List<? extends BaseUserInfo> successUserList);

    // ========================== 作品数据同步抽象方法 ==========================

    /**
     * 获取用户的所有作品（支持分页加载）
     * <p>
     * 子类需实现平台特有作品列表的获取逻辑，包括分页查询、多轮请求等，
     * 最终返回该用户的所有作品数据。该方法完全由子类控制，灵活性最高。
     * </p>
     *
     * @param userInfo 用户信息对象（用于关联作品所属用户）
     * @param nowTime 统一的更新时间戳（用于设置作品更新时间）
     * @return 该用户的所有作品数据列表，若无可返回空列表
     * @throws Exception 处理过程中可能抛出的异常（由上层捕获）
     */
    protected abstract List<? extends BaseWorkData> fetchUserAllWorks(BaseUserInfo userInfo, String nowTime,Integer num) throws Exception;

    /**
     * 创建作品数据请求的重试决策器
     * <p>
     * 子类可实现作品接口的特有重试策略，根据响应码或响应内容判断是否需要重试。
     * 若返回null，将使用默认决策器（不重试）。
     * </p>
     *
     * @param workData 当前处理的作品对象
     * @return 重试决策器实例，或null使用默认实现
     */
    protected abstract HttpRequestUtil.RetryDecision createWorkRetryDecision(BaseWorkData workData);

    /**
     * 批量更新作品数据到数据库
     * <p>
     * 子类需实现作品数据的批量入库逻辑（如批量INSERT或UPDATE），
     * 建议使用数据库的批量操作API，减少SQL执行次数。
     * </p>
     *
     * @param worksData 待更新的作品数据列表（非空）
     */
    protected abstract void batchUpdateWorksData(List<? extends BaseWorkData> worksData);

    /**
     * 构造作品相关API的请求头（通用头，非用户级）
     * <p>
     * 子类需实现作品接口的通用请求头组装，如认证信息（Token）、Content-Type等，
     * 与用户头信息的区别在于：此头信息为平台级通用，不包含用户特有信息。
     * </p>
     *
     * @return 组装好的请求头Map（键为头名称，值为头内容）
     */
    protected abstract Map<String, String> createHeaders(BaseUserInfo userInfo);
}